﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using DevExpress.XtraEditors;
using System.Threading.Tasks;
using System.ServiceModel;
using ProgNetComponentsDx.Options;
using System.ServiceModel.Description;
using System.Diagnostics;
using ProgNetComponentsDx.Components;
using System.Collections.Concurrent;
using System.Threading;
using ProgNetComponentsDx.Tasks;
using ProgNetComponentsDx.Forms;
using System.IO;
using BuildHelper.Admin;

namespace BuildHelper.Server
{
    public partial class BhServer : XtraForm
    {
        public static BhServer Instance { get; private set; }
        public static void ShowInstance()
        {
            FormMain.Instance.Invoke(new Action(() =>
            {
                if (Instance == null)
                {
                    Instance = new BhServer();
                }
                if (!Instance.Visible)
                    Instance.Show(FormMain.Instance);
                try
                {
                    User32.SetForegroundWindow(Instance.Handle);
                }
                catch { }
            }));
        }
        BindingList<User> userList = new BindingList<User>();

        #region Sprawdzanie wersji
        BaseEventTask taskGetVersionAll = new BaseEventTask();
        List<string> GetVersionAllList = new List<string>();
        void taksGetVersionAll_TaskPerformed(object sender, TaskResultEventArgs e)
        {
            GetVersionAllList.Clear();
            List<Task> l = new List<Task>();
            foreach (User u in userList.ToArray())
            {
                try
                {
                    Task<Version> t1 = Task.Factory.StartNew((user) =>
                    {
                        return ((User)user).UpdateServerServiceCallback.GetVersion();
                    }, u);
                    t1.ContinueWith((t) =>
                    {
                        if (t.IsFaulted)
                            GetVersionAllList.Add("Błąd pobierania wersji dla {0}".FormatMe(((User)t.AsyncState).Name));
                        else
                            GetVersionAllList.Add("{0} ma wersję {1}".FormatMe(((User)t.AsyncState).Name, t.Result));
                    });
                    l.Add(t1);
                }
                catch
                {
                    GetVersionAllList.Add("Błąd pobierania wersji dla {0}".FormatMe(u.Name));
                }
            }
            Task.WaitAll(l.ToArray());
            FormMain.Instance.Invoke(new Action(() =>
            {
                using (FormMemoInput f = new FormMemoInput(GetVersionAllList.AsString(Constants.NewLineLinux)))
                {
                    f.ReadOnly = true;
                    f.ShowDialog();
                }
            }));
            GetVersionAllList.Clear();
        }
        private void bbiCheckVersionAll_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e)
        {
            taskGetVersionAll.Run();
        }
        #endregion

        protected BhServer()
        {
            InitializeComponent();
            if (BhServerService.Instance != null)
            {
                var users = BhServerService.Instance.GetUsers();
                foreach (var u in users)
                    userList.Add(u);
                listBoxControl1.DataSource = userList;
                
                BhServerService.Instance.UserConnected += new UserActionHandler(Instance_UserConnected);
                BhServerService.Instance.UserDisconnected += new UserActionHandler(Instance_UserDisconnected);
                
                taskGetVersionAll.TaskPerformed += new PerformTaskHandler(taksGetVersionAll_TaskPerformed);
            }
        }

        void Instance_UserDisconnected(User user)
        {
            lock (userList)
            {
                userList.Remove(user);
            }
        }

        void Instance_UserConnected(User user)
        {
            lock (userList)
            {
                if (!userList.Contains(user))
                    userList.Add(user);
            }
        }
        
        protected override void OnFormClosed(FormClosedEventArgs e)
        {
            BhServerService.Instance.UserConnected -= new UserActionHandler(Instance_UserConnected);
            BhServerService.Instance.UserDisconnected -= new UserActionHandler(Instance_UserDisconnected);
                
            Instance = null;
            base.OnFormClosed(e);
        }

        List<Client> addCli = new List<Client>();
        private void btnAddVersion_Click(object sender, EventArgs e)
        {
            if (addCli.Count < 4)
            {
                Client c = new Client();
                c.Name = "plukom" + addCli.Count;
                c.Run();
                addCli.Add(c);
            }
            else
            {
                foreach (Client c in addCli)
                {
                    c.Close();
                }
                addCli.Clear();
            }
        }

        private void bbiCheckVersion_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e)
        {
            User u = listBoxControl1.SelectedItem as User;
            if (u != null)
            {
                Task task = Task.Factory.StartNew((user) =>
                {
                    this.ShowDxInfoDialog("{0} ma wersję {1}".FormatMe(((User)user).Name, ((User)user).UpdateServerServiceCallback.GetVersion().ToString()));
                }, u);
                task.ContinueWith((t) => 
                {
                    if (t.IsFaulted)
                        this.ShowDxInfoDialog("Błąd pobierania wersji dla {0}".FormatMe(((User)t.AsyncState).Name));
                });
            }
        }

        private void bbinInfo_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e)
        {
            User u = listBoxControl1.SelectedItem as User;
            if (u != null)
            {
                using (InputForm input = new InputForm())
                {
                    input.Text = Application.ProductName;
                    if (input.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                    {
                        Task task = Task.Factory.StartNew((userObject) =>
                        {
                            User user = userObject as User;
                            if (user != null)
                            {
                                user.UpdateServerServiceCallback.Info(input.ResultString);
                            }
                        }, u);
                    }
                }
            }
        }

        private void bbiLogFile_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e)
        {
            User u = listBoxControl1.SelectedItem as User;
            
            if (u != null)
            {
                if (u.Streams.ContainsKey(StreamId.LogStream))
                {
                    this.ShowDxInfoDialog("Pobieranie logu w toku");
                    return;
                }
                
                Task task = Task.Factory.StartNew((userObject) =>
                {
                    User user = userObject as User;
                    if (user != null)
                        user.UpdateServerServiceCallback.GetLogFile();
                }, u);
            }
        }

        private void bbiQuery_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e)
        {
             User u = listBoxControl1.SelectedItem as User;

             if (u != null)
             {
                 if (u.Streams.ContainsKey(StreamId.DataTable))
                 {
                     this.ShowDxInfoDialog("Operacja w toku");
                     return;
                 }
                 using (FormQuery f = new FormQuery(u.UserId, u.Name))
                 {
                     f.ShowDialog();
                 }
             }

        }
    }

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    public class BhServerService : IUpdateServerService
    {
        public event UserActionHandler UserConnected;
        public event UserActionHandler UserDisconnected;

        private Task serverTask;
        private ConcurrentDictionary<long, User> AppUsers = new ConcurrentDictionary<long, User>();
        private long AppUsersId;

        public static void Run()
        {
            Instance = new BhServerService();
            Instance.Start();
        }
        public static BhServerService Instance
        {
            get;
            private set;
        }

        #region Properties
        public bool AddLogs
        {
            get;
            set;
        }
        public string LogFile
        {
            get;
            private set;
        }
        public string HostIp
        {
            get { return OptionCache.GetOptionStringValue(Init.InitFormOptionNames.Kontakty_Group.Server.HostIp); }
        }
        public int Port
        {
            get { return OptionCache.GetOptionValue(Init.InitFormOptionNames.Kontakty_Group.Server.Port).AsInt(4297); }
        }
        #endregion

        #region CTOR
        public BhServerService()
        {
            LogFile = Application.StartupPath + "\\Server.log";
            AddLogs = true;
        }
        #endregion

        public event MessageReceivedHandler MessageReceived;

        protected void OnMessageReceived(string msg)
        {
            if (MessageReceived != null)
                MessageReceived(msg);
            if (AddLogs)
                Task.Factory.StartNew(() => { "{0} {1}".FormatMe(DateTime.Now.ToString("T"), msg).AppendToFile(LogFile); });
        }
        public void Start()
        {
            if (serverTask == null)
            {
                serverTask = Task.Factory.StartNew(StartServer);
                serverTask.ContinueWith(ServerClose);
            }
        }
        public void Stop()
        {
            if (serverTask != null)
            {
                Cancel = true;
            }
        }
        private bool Cancel;
        private void StartServer()
        {
            Cancel = false;
            Uri baseAddress = new Uri("net.tcp://{0}:{1}/BHServer".FormatMe(HostIp, Port));

            using (ServiceHost host = new ServiceHost(BhServerService.Instance, baseAddress))
            {
                try
                {
                    host.Opening += new EventHandler((sender, e) => { OnMessageReceived("Uruchamianie serwera."); });
                    host.Opened += new EventHandler((sender, e) => { OnMessageReceived("Serwer uruchomiony."); });
                    host.Closing += new EventHandler((sender, e) => { OnMessageReceived("Zatrzymywanie serwera."); });
                    host.Closed += new EventHandler((sender, e) => { OnMessageReceived("Serwer zatrzymany."); });

                    var binding = new NetTcpBinding();
                    binding.MaxReceivedMessageSize = 1024 * 1024 * 160;
                    binding.TransactionFlow = false;
                    binding.Security.Transport.ProtectionLevel = System.Net.Security.ProtectionLevel.EncryptAndSign;
                    binding.Security.Transport.ClientCredentialType = TcpClientCredentialType.Windows;
                    binding.Security.Mode = SecurityMode.None;

                    var updateEndPoint = host.AddServiceEndpoint(typeof(IUpdateServerService), binding, "/Update");
                    //var chatEndPoint = host.AddServiceEndpoint(typeof(Comunication.IChatService), binding, "/Chat");

                    ServiceMetadataBehavior metadataBehavior = host.Description.Behaviors.Find<ServiceMetadataBehavior>();

                    if (metadataBehavior == null)
                    {
                        metadataBehavior = new ServiceMetadataBehavior();
                        host.Description.Behaviors.Add(metadataBehavior);
                    }
                    
                    host.Open();
                    while (!Program.IsExit && !Cancel)
                    {
                        System.Threading.Thread.Sleep(50);
                    }
                    host.Close();
                }
                catch (Exception ex)
                {
                    OnMessageReceived("Exception: {0}\r\n\tStack:\r\n{1}".FormatMe(ex.Message, ex.StackTrace.ToLines(allowDuplitates: true).AsString(Constants.NewLineLinux + "\t")));
                }
            }
            Cancel = true;
        }
        private void ServerClose(Task t)
        {
            serverTask = null;
        }
        
        
        internal void RemoveUser(long id)
        {
            User u;
            if (AppUsers.TryRemove(id, out u))
            {
                if (UserDisconnected != null)
                    UserDisconnected(u);
            }
        }
        
        #region IUpdateServerService
        public Version GetLatestVersion()
        {
            return new Version("1.1.1.1");
        }

        public long Connect(string name)
        {
            long id = Interlocked.Increment(ref AppUsersId);
            User u = new User(id, name);
            u.UpdateServerServiceCallback = OperationContext.Current.GetCallbackChannel<IUpdateServerServiceCallback>();
            AppUsers.TryAdd(id, u);
            u.Task = new MonitorTask(u);
            u.Task.Run();
            
            Task.Factory.StartNew((userObject) =>
            {
                User user = userObject as User;
                if (UserConnected != null)
                    UserConnected(user);
            }, u);

            return id;
        }

        public void Disconnect(long id)
        {
            RemoveUser(id);
        }

        public void Beep()
        {
        }

        public void SendStreamPart(long Id, string streamId, byte[] buffer, int bufferSize)
        {
            User u = null;
            if (AppUsers.TryGetValue(Id, out u))
            {
                Stream s = null;
                if (u.Streams.TryGetValue(streamId, out s))
                {
                    s.Write(buffer, 0, bufferSize);
                }
            }
        }

        public bool BeginSendingStream(long Id, string streamId)
        {
            User u = null;
            if (AppUsers.TryGetValue(Id, out u))
            {
                Stream stream = null;
                if (!u.Streams.TryGetValue(streamId, out stream))
                {
                    u.Streams.TryAdd(streamId, GetStream(u, streamId));
                    return true;
                }
            }
            return false;
        }

        public void EndSendingStream(long Id, string streamId)
        {
            User user = null;
            if (AppUsers.TryGetValue(Id, out user))
            {
                ProcessStream(user, streamId);
            }
        }

        #endregion

        private class MonitorTask : ProgNetComponentsDx.Tasks.BaseTask
        {
            public MonitorTask(User u)
            {
                User = u;
            }
            public User User { get; private set; }
            
            protected override ProgNetComponentsDx.Tasks.TaskStatus PerformTask(System.Threading.CancellationToken token)
            {
                while (true)
                {
                    token.ThrowIfCancellationRequested();
                    if (Instance.AppUsers.ContainsKey(User.UserId))
                    {
                        try
                        {
                            if (User.UpdateServerServiceCallback != null)
                                User.UpdateServerServiceCallback.Notify();
                        }
                        catch
                        {
                            Instance.RemoveUser(User.UserId);
                            return ProgNetComponentsDx.Tasks.TaskStatus.Finished;
                        }
                    }
                    else
                    {
                        return ProgNetComponentsDx.Tasks.TaskStatus.Finished;
                    }
                    for (int i = 0; i < 10; i++)
                    {
                        token.ThrowIfCancellationRequested();
                        System.Threading.Thread.Sleep(100);
                    }
                }
            }
        }

        public void NotifyNewVersion(Version newVersion)
        {
            foreach (var u in AppUsers.Values)
            {
                try { u.UpdateServerServiceCallback.NotifyNewVersion(newVersion); }
                catch
                {
                    RemoveUser(u.UserId);
                }
            }
        }

        public User[] GetUsers()
        {
            return AppUsers.Values.ToArray();
        }

        private Stream GetStream(User u, string streamId)
        {
            try
            {
                switch (streamId)
                {
                    case StreamId.LogStream:
                        return File.OpenWrite(GlobalTools.FilesTools.GetTemplLogFile(u.Name + "_error_"));
                    default:
                        return new MemoryStream();
                }
            }
            catch { }
            return null;
        }
        
        private static void ProcessStream(User user, string streamId)
        {
            Stream stream = null;
            if (user.Streams.TryGetValue(streamId, out stream))
            {
                switch (streamId)
                {
                    case StreamId.LogStream:
                        ProcessLog(user, stream);
                        break;
                    default:
                        user.FinishedStreams.TryAdd(streamId, true);
                        break;
                }
            }
        }

        private static void ProcessLog(User user, Stream stream)
        {
            try
            {
                stream.Flush();
                stream.Close();
                stream.Dispose();
            }
            catch { }

            Task.Factory.StartNew((userObject) =>
            {
                var u = userObject as User;
                if (u != null)
                {
                    Stream fs = null;
                    u.Streams.TryGetValue(StreamId.LogStream, out fs);
                    if (fs is FileStream)
                    {
                        if (FormMain.Instance.ShowDxYNQuestionDialog("Otrzymano logi od {0}. Chcesz je zobaczyc?".FormatMe(u.Name)) == System.Windows.Forms.DialogResult.Yes)
                        {
                            Process.Start(((FileStream)fs).Name);
                        }
                    }
                    u.Streams.TryRemove(StreamId.LogStream, out fs);
                }
            }, user);
        }

        public void GetTable(long userId, string ss, Action<DataTable> resultAction)
        {
            DataTable dt = null;
            try
            {
                User u = null;
                if (AppUsers.TryGetValue(userId, out u))
                {
                    u.UpdateServerServiceCallback.GetDataTable(ss);
                    while (!u.IsFinishedStream(StreamId.DataTable))
                    {
                        System.Threading.Thread.Sleep(100);
                    }
                    Stream s = u.GetFinishedStream(StreamId.DataTable);
                    if (s != null)
                    {
                        s.Seek(0, SeekOrigin.Begin);
                        dt = new DataTable();
                        dt.ReadXml(s);
                        if (resultAction != null)
                            resultAction(dt);
                    }
                }
            }
            catch { }
        }
    }

    public delegate void UserActionHandler(User user);

    public static class StreamId
    {
        public const string LogStream = "LogFile";
        public const string DataTable = "DataTable";
    }
    public delegate void MessageReceivedHandler(string msg);
}
